/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package network;

import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.ArrayList;
import network.messages.*;

/**
 * Classe que representa um servidor
 * @owner Victor
 */
public class Servidor extends Thread
{

	private ServerSocket serverSocket;
	private ArrayList<ChildSocket> childSockets = new ArrayList<ChildSocket>();
	private ArrayList<NetworkListener> listeners = new ArrayList<NetworkListener>();
	private int lastId = 0;
	private int _porta;
	private boolean closed = false;

	public Servidor()
	{
	}

	/**
	 * Ouve os sockets filhos, e caso algum se conecte adiciona ele nos sockets filhos
	 * @owner Victor
	 */
	private void ListenChilds()
	{
		if (childSockets.size() < 2)
		{
			try
			{
				ChildSocket socketFilho = new ChildSocket(serverSocket.accept(), this);
				childSockets.add(socketFilho);

				MensagemAssignId assignId = new MensagemAssignId();
				assignId.SetId(lastId);
				socketFilho.SendMessage(assignId.GetJSONString());

				LogStringMessage("Filho conectado com id = " + lastId);

				lastId++;
			} catch (IOException err)
			{
				err.printStackTrace();
			}
		}
	}

	/**
	 * Ouve as mensagens dos sockets filhos, ficando atento as mensagens de logout, e além disso manda a mensagens para os outros filhos
	 * @owner Victor
	 */
	private void ParseMessageFromChild(String message, ChildSocket socket)
	{
		Mensagem mensagem = MensagemFactory.GetMensagem(message);

		if (mensagem instanceof MensagemLogout)
		{
			MensagemLogout logout = (MensagemLogout) mensagem;
			LogStringMessage("Filho desconectado com id = " + logout.GetFrom());
			socket.interrupt();
			childSockets.remove(socket);
		}

		LogStringMessage("Mensagem chegou: " + mensagem.GetJSONString());

		for (ChildSocket childSocket : childSockets)
		{
			if (childSocket != socket)
			{
				LogMessage(mensagem);
				childSocket.SendMessage(mensagem.GetJSONString());
			}
		}
	}

	/**
	 * Escreve uma mensagem no log do servidor
	 * @owner Victor
	 */
	private void LogStringMessage(String mensagem)
	{
		for (NetworkListener listener : listeners)
		{
			listener.OnMensagemLog(mensagem);
		}
	}

	/**
	 * Escreve uma mensagem no log do servidor
	 * @owner Victor
	 */
	private void LogMessage(Mensagem mensagem)
	{
		for (NetworkListener listener : listeners)
		{
			listener.OnMensagemChegou(mensagem);
		}
	}

	/**
	 * Adiciona um listener
	 * @owner Victor
	 */
	public void AddListener(NetworkListener listener)
	{
		listeners.add(listener);
	}

	/**
	 * Remove um listener
	 * @owner Victor
	 */
	public void RemoveListener(NetworkListener listener)
	{
		listeners.remove(listener);
	}

	/**
	 * Abre a conexão do servidor
	 * @owner Victor
	 */
	public boolean Open(int porta)
	{
		boolean erro = false;

		try
		{
			_porta = porta;
			serverSocket = new ServerSocket(porta);
			LogStringMessage("Servidor aberto");
			this.start();
		} catch (IOException err)
		{
			erro = true;
			err.printStackTrace();
		}

		return erro;
	}

	/**
	 * Fecha a conexão com o servidor
	 * @owner Victor
	 */
	public boolean Close()
	{
		boolean erro = false;

		try
		{
			serverSocket.close();
			this.interrupt();
			closed = true;
		} catch (IOException err)
		{
			erro = true;
			err.printStackTrace();
		}

		return erro;
	}

	@Override
	/**
	 * Roda a thread, esperando sockets filhos
	 * @owner Victor
	 */
	public void run()
	{
		while (!closed)
		{
			ListenChilds();
		}
	}

	/**
	 * Tupla de sockets filhos desse servidor
	 * @author Victor
	 */
	private class ChildSocket extends Thread
	{

		private Servidor servidor;
		private Socket socket;
		private Formatter formatter;
		private Scanner scanner;

		/**
		 * Envia uma string para esse cliente
		 * @owner Victor
		 */
		public void SendMessage(String message)
		{
			formatter.format("%s\n", message);
			formatter.flush();
		}

		public ChildSocket(Socket socketListener, Servidor servidor)
		{
			this.servidor = servidor;

			socket = socketListener;

			try
			{
				formatter = new Formatter(socket.getOutputStream());
				scanner = new Scanner(socket.getInputStream());
				this.start();
			} catch (IOException err)
			{
				err.printStackTrace();
			}
		}

		@Override
		/**
		 * Roda a thread
		 * @author Victor
		 */
		public void run()
		{
			while (scanner.hasNextLine())
			{
				try
				{
					String line = scanner.nextLine();
					servidor.ParseMessageFromChild(line, this);
				} catch (Exception err)
				{
				}
			}
		}
	}
}
